package com.piece.module.flowable.service;

import com.piece.core.framework.exception.ServerException;
import com.piece.core.framework.support.bootstrap.DataTable;
import com.piece.core.framework.support.bootstrap.QueryTable;
import com.piece.core.framework.support.convert.Convert;
import com.piece.core.framework.util.object.ObjectUtil;
import com.piece.core.framework.util.string.JsonUtil;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.log.client.dto.UserDTO;
import com.piece.core.log.config.UserContextHolder;
import com.piece.module.flowable.constants.ExceptionConstants;
import com.piece.module.flowable.constants.ProcessConstants;
import com.piece.module.flowable.dto.WfMetaInfo;
import com.piece.module.flowable.dto.WfModel;
import com.piece.module.flowable.enums.FormType;
import com.piece.module.flowable.factory.FlowServiceFactory;
import com.piece.module.flowable.model.WfForm;
import com.piece.module.flowable.util.FlowUtil;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 流程模型Service
 */
@Service
@Transactional
public class WfModelService extends FlowServiceFactory {

    @Resource
    private WfFormService wfFormService;

    @Resource
    private WfDeployFormService wfDeployFormService;

    public void insert(WfModel wfModel) {
        Model model = repositoryService.newModel();
        model.setName(wfModel.getModelName());
        model.setKey(wfModel.getModelKey());
        model.setCategory(wfModel.getCategory());
        String metaInfo = buildMetaInfo(new WfMetaInfo(), wfModel.getDescription());
        model.setMetaInfo(metaInfo);
        // 保存流程模型
        repositoryService.saveModel(model);
    }

    public void update(WfModel wfModel) {
        Model model = repositoryService.getModel(wfModel.getModelId());
        if (ObjectUtil.isNull(model)) {
            throw new ServerException(ExceptionConstants.NOT_EXIST, "流程模型-" + wfModel.getModelName());
        }

        model.setCategory(wfModel.getCategory());
        WfMetaInfo metaInfo = JsonUtil.toBean(model.getMetaInfo(), WfMetaInfo.class);
        String metaInfoStr = buildMetaInfo(new WfMetaInfo(), wfModel.getDescription());
        model.setMetaInfo(metaInfoStr);
        // 保存流程模型
        repositoryService.saveModel(model);
    }

    /**
     * 创建模型
     */
    public void saveModel(WfModel wfModel) {
        Model model = repositoryService.getModel(wfModel.getModelId());
        if (ObjectUtil.isNull(model)) {
            throw new ServerException(ExceptionConstants.NOT_EXIST, "流程模型-" + wfModel.getModelName());
        }

        BpmnModel bpmnModel = FlowUtil.getBpmnModel(wfModel.getBpmnXml());
        if (ObjectUtil.isEmpty(bpmnModel)) {
            throw new ServerException(ExceptionConstants.NOT_EXIST, "模型设计-" + wfModel.getModelName());
        }

        String processName = bpmnModel.getMainProcess().getName();
        // 获取开始节点
        StartEvent startEvent = FlowUtil.getStartEvent(bpmnModel);
        if (ObjectUtil.isNull(startEvent)) {
            throw new ServerException(ExceptionConstants.NOT_EXIST, "开始节点");
        }

        // 获取开始节点配置的表单Key
        if (StringUtil.isEmpty(startEvent.getFormKey())) {
            throw new ServerException(ExceptionConstants.MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG, null);
        }

        Model newModel;
        if (Boolean.TRUE.equals(wfModel.getNewVersion())) {
            newModel = repositoryService.newModel();
            newModel.setName(processName);
            newModel.setKey(model.getKey());
            newModel.setCategory(wfModel.getCategory());
            newModel.setMetaInfo(model.getMetaInfo());
            newModel.setVersion(model.getVersion() + 1);
        } else {
            newModel = model;
            newModel.setName(processName);
            newModel.setCategory(wfModel.getCategory());
        }

        // 保存流程模型
        repositoryService.saveModel(newModel);
        // 保存 BPMN XML
        repositoryService.addModelEditorSource(newModel.getId(), FlowUtil.getBpmnXml(bpmnModel));
    }

    /**
     * 更新模型到最新版
     */
    public void latestModel(String modelId) {
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtil.isNull(model)) {
            throw new ServerException(ExceptionConstants.NOT_EXIST, "流程模型-" + modelId);
        }

        String bpmnXml = readBpmnXml(modelId);
        Integer latestVersion = repositoryService.createModelQuery()
                .modelKey(model.getKey())
                .latestVersion()
                .singleResult()
                .getVersion();
        if (model.getVersion().equals(latestVersion)) {
            throw new ServerException("当前版本已是最新版！");
        }

        Model newModel = repositoryService.newModel();
        newModel.setName(model.getName());
        newModel.setKey(model.getKey());
        newModel.setCategory(model.getCategory());
        newModel.setMetaInfo(model.getMetaInfo());
        newModel.setVersion(latestVersion + 1);
        // 保存流程模型
        repositoryService.saveModel(newModel);
        // 保存 BPMN XML
        repositoryService.addModelEditorSource(newModel.getId(), StringUtil.getBytes(bpmnXml, StandardCharsets.UTF_8));
    }

    /**
     * 部署模型
     */
    public void deployModel(String modelId) throws Exception {
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtil.isNull(model)) {
            throw new ServerException(ExceptionConstants.NOT_EXIST, "流程模型-" + modelId);
        }

        // 获取流程图
        String bpmnXml = readBpmnXml(modelId);
        BpmnModel bpmnModel = FlowUtil.getBpmnModel(bpmnXml);
        String processName = model.getName() + ProcessConstants.BPMN_SUFFIX;
        // 部署流程
        Deployment deployment = repositoryService.createDeployment()
                .name(model.getName())
                .key(model.getKey())
                .addBpmnModel(processName, bpmnModel)
                .category(model.getCategory())
                .deploy();
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();
        // 设置流程分类
        for (ProcessDefinition processDefinition : list) {
            if (StringUtil.isNotBlank(model.getCategory())) {
                repositoryService.setProcessDefinitionCategory(processDefinition.getId(), model.getCategory());
            }
        }
        // 保存部署表单
        wfDeployFormService.saveInternal(deployment.getId(), bpmnModel);
    }

    private String buildMetaInfo(WfMetaInfo metaInfo, String description) {
        if (StringUtil.isNotEmpty(description)) {
            metaInfo.setDescription(description);
        }
        if (StringUtil.isNotEmpty(metaInfo.getCreateUser())) {
            UserDTO user = UserContextHolder.get();
            metaInfo.setCreateUser(user.getName());
        }
        return JsonUtil.toJson(metaInfo);
    }

    /**
     * 删除流程模型
     */
    public void delete(Collection<String> ids) {
        ids.forEach(id -> {
            Model model = repositoryService.getModel(id);
            if (ObjectUtil.isNull(model)) {
                throw new ServerException(ExceptionConstants.NOT_EXIST, "流程模型-" + id);
            }
            repositoryService.deleteModel(id);
        });
    }

    /**
     * 查询流程模型
     */
    public WfModel findModel(String modelId) {
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtil.isNull(model)) {
            throw new ServerException(ExceptionConstants.NOT_EXIST, "流程模型-" + modelId);
        }

        String bpmnXml = readBpmnXml(modelId);
        WfModel vo = new WfModel();
        vo.setModelId(model.getId());
        vo.setModelName(model.getName());
        vo.setModelKey(model.getKey());
        vo.setCategory(model.getCategory());
        vo.setCreateTime(model.getCreateTime());
        vo.setVersion(model.getVersion());
        vo.setBpmnXml(bpmnXml);
        WfMetaInfo metaInfo = JsonUtil.toBean(model.getMetaInfo(), WfMetaInfo.class);
        if (ObjectUtil.isNotNull(metaInfo)) {
            vo.setDescription(metaInfo.getDescription());
            vo.setFormType(metaInfo.getFormType());
            vo.setFormId(metaInfo.getFormId());
            if (FormType.PROCESS.getType().equals(metaInfo.getFormType())) {
                WfForm form = wfFormService.findById(metaInfo.getFormId());
                vo.setContent(form.getContent());
            }
        }

        return vo;
    }

    /**
     * 查询流程模型列表
     */
    public DataTable<WfModel> findPage(QueryTable queryTable, String modelKey) {
        DataTable dataTable = new DataTable(queryTable);

        ModelQuery modelQuery;
        if (StringUtil.isNotEmpty(modelKey)) {
            modelQuery = repositoryService.createModelQuery().modelKey(modelKey).orderByModelVersion().desc();
        } else {
            modelQuery = repositoryService.createModelQuery().latestVersion().orderByCreateTime().desc();
        }
        String Q_modelKey = Convert.toStr(queryTable.getParams().get("Q_modelKey_S_EQ"));
        if (StringUtil.isNotEmpty(Q_modelKey)) {
            modelQuery.modelKey(Q_modelKey);
        }
        String Q_modelName = Convert.toStr(queryTable.getParams().get("Q_modelName_S_LK"));
        if (StringUtil.isNotEmpty(Q_modelName)) {
            modelQuery.modelNameLike("%" + Q_modelName + "%");
        }
        String Q_category = Convert.toStr(queryTable.getParams().get("Q_category_S_EQ"));
        if (StringUtil.isNotEmpty(Q_category)) {
            modelQuery.modelCategory(Q_category);
        }

        long totalCount = modelQuery.count();
        int offset = queryTable.getLength() * (queryTable.getCurrentPage() - 1);
        if (StringUtil.isNotEmpty(modelKey)) {
            // 不显示最新版
            totalCount--;
            offset++;
        }
        if (totalCount <= 0) {
            return dataTable;
        }

        List<Model> modelList = modelQuery.listPage(offset, queryTable.getLength());
        List<WfModel> wfModelList = modelList.stream().map(model -> {
            WfModel vo = new WfModel();
            vo.setModelId(model.getId());
            vo.setModelName(model.getName());
            vo.setModelKey(model.getKey());
            vo.setCategory(model.getCategory());
            vo.setCreateTime(model.getCreateTime());
            vo.setVersion(model.getVersion());
            WfMetaInfo metaInfo = JsonUtil.toBean(model.getMetaInfo(), WfMetaInfo.class);
            if (ObjectUtil.isNotNull(metaInfo)) {
                vo.setDescription(metaInfo.getDescription());
                vo.setFormType(metaInfo.getFormType());
                vo.setFormId(metaInfo.getFormId());
            }

            return vo;
        }).collect(Collectors.toList());

        dataTable.setData(wfModelList);
        dataTable.setRecordsTotal(Convert.toInt(totalCount));
        return dataTable;
    }

    public String readBpmnXml(String modelId) {
        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
        return StringUtil.string(bpmnBytes, StandardCharsets.UTF_8);
    }
}
